﻿
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using RenewalProjects.Libraries.ParamGate.Attributes;
using RenewalProjects.Libraries.ParamGate.Exceptions;

namespace RenewalProjects.Libraries.ParamGate.Tests
{
	[TestFixture]
	public class ParamGateHandlerFixture
	{
		#region Test Methods

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Cannot_Accept_Null_Parameter_List()
		{
			ParamGateHandler.ProcessArguments(null, new object());
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Cannot_Accept_Null_Parameter_Container()
		{
			ParamGateHandler.ProcessArguments(new string[] {}, null);
		}

		[Test]
		public void Expected_Parameter_Specified()
		{
			string[] testParams = new string[] { "-Name", "Danny" };
			OneExpectedParameter param = new OneExpectedParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual("Danny", param.Name);
		}

	    [Test]
        [ExpectedException(typeof(ParamGateException))]
	    public void Expected_Parameter_Specified_Without_Value()
	    {
            string[] testParams = new string[] { "-Name" };
            OneExpectedParameter param = new OneExpectedParameter();
            ParamGateHandler.ProcessArguments(testParams, param);
	    }

		[Test]
		[ExpectedException(typeof(ParamGateException))]
		public void May_Not_Specify_Parameters_Twice()
		{
			string[] testParams = new string[] { "-Name", "Danny", "-Name", "Johnny" };
			OneExpectedParameter param = new OneExpectedParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		[ExpectedException(typeof(ParamGateException))]
		public void Expected_Parameter_Not_Specified()
		{
			string[] testParams = new string[] { };
			OneExpectedParameter param = new OneExpectedParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		public void Explicit_Optional_Parameter_Specified()
		{
			string[] testParams = new string[] { "-Message", "Good day" };
			OneExplicitOptionalParameter param = new OneExplicitOptionalParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual("Good day", param.Message);
		}

		[Test]
		public void Explicit_Optional_Parameter_Not_Specified()
		{
			string[] testParams = new string[] { };
			OneExplicitOptionalParameter param = new OneExplicitOptionalParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.IsNull(param.Message);
		}

		[Test]
		public void Implicit_Optional_Parameter_Specified()
		{
			string[] testParams = new string[] { "-Message", "Good day" };
			OneImplicitOptionalParameter param = new OneImplicitOptionalParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual("Good day", param.Message);
		}

		[Test]
		public void Implicit_Optional_Parameter_Not_Specified()
		{
			string[] testParams = new string[] { };
			OneImplicitOptionalParameter param = new OneImplicitOptionalParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.IsNull(param.Message);
		}

		[Test]
		[ExpectedException(typeof(ParamGateException))]
		public void Unknown_Parameter_Specified()
		{
			string[] testParams = new string[] { "-Blah", "Aha" };
			OneImplicitOptionalParameter param = new OneImplicitOptionalParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		public void No_Parameter_Specified_Use_Expected_Default()
		{
			string[] testParams = new string[] { "I know you can see me" };
			ExpectedDefaultParameter param = new ExpectedDefaultParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual( "I know you can see me", param.Message);
		}

		[Test]
		[ExpectedException(typeof(ParamGateException))]
		public void No_Parameter_Specified_For_Expected_Default_To_Be_Used_But_No_Values_Given()
		{
			string[] testParams = new string[] {  };
			ExpectedDefaultParameter param = new ExpectedDefaultParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		[ExpectedException(typeof(ParamGateException))]
		public void No_Multiple_Defaults_Allowed()
		{
			string[] testParams = new string[] { "You cannot do that" };
			MultipleDefaultParameters param = new MultipleDefaultParameters();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		public void No_Parameter_Specified_Use_Optional_Default()
		{
			string[] testParams = new string[] { "I know you can see me" };
			OptionalDefaultParameter param = new OptionalDefaultParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual( "I know you can see me", param.Message);
		}

		[Test]
		[ExpectedException(typeof(ParamGateException))]
		public void No_Parameter_Specified_Also_No_Default_Specified()
		{
			string[] testParams = new string[] { "I know you can see me" };
			OneImplicitOptionalParameter param = new OneImplicitOptionalParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		public void No_Parameter_Specified_To_Optional_Default_To_Be_Used_But_No_Values_Given()
		{
			string[] testParams = new string[] {  };
			OptionalDefaultParameter param = new OptionalDefaultParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.IsNull(param.Message);
		}

		[Test]
		public void Populate_Int32_Parameter()
		{
			string[] testParams = new string[] { "-Number", "1" };
			Int32Parameter param = new Int32Parameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual(1, param.Number);
		}

		[Test]
		public void Populate_Bool_Parameter()
		{
			string[] testParams = new string[] { "-Setting", "True" };
			BoolParameter param = new BoolParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual(true, param.Setting);
		}

		[Test]
		public void Detect_Property_As_Switch_On()
		{
			string[] testParams = new string[] { "-Setting" };
			BoolParameter param = new BoolParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual(true, param.Setting);
		}

		[Test]
		public void Detect_Property_As_Switch_Not_Set()
		{
			string[] testParams = new string[] { };
			BoolParameter param = new BoolParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual(false, param.Setting);
		}

		[Test]
		public void Detect_Property_As_Switch_Not_Set_Not_Affecting_Previous_Value()
		{
			string[] testParams = new string[] { };
			BoolParameter param = new BoolParameter();
			param.Setting = true; // Set to true to make sure it doesn't get affected
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual(true, param.Setting);
		}

		[Test]
		[ExpectedException(typeof(ParamGateException))]
		public void Parameter_Used_As_Switch_But_Not_Of_Type_Boolean()
		{
			string[] testParams = new string[] { "-Message" };
			OneImplicitOptionalParameter param = new OneImplicitOptionalParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		public void Populate_String_Array_Parameter()
		{
			string[] testParams = new String[] { "-Values", "A", "B", "C" };
			StringArrayParameter param = new StringArrayParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.IsNotNull(param.Values);
			CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, param.Values);
		}

		[Test]
		public void Populate_IList_Parameter()
		{
			string[] testParams = new String[] { "-Values", "A", "B", "C", "-OtherValues", "D", "E", "F" };
			IListParameter param = new IListParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.IsNotNull(param.Values);
			CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, param.Values);
			Assert.IsNotNull(param.OtherValues);
			CollectionAssert.AreEqual(new string[] { "D", "E", "F" }, param.OtherValues);
		}

		[Test]
		public void Populate_IEnumerable_Parameter()
		{
			string[] testParams = new String[] { "-Values", "A", "B", "C", "-OtherValues", "D", "E", "F" };
			IEnumerableParameter param = new IEnumerableParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.IsNotNull(param.Values);
			CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, param.Values);
			Assert.IsNotNull(param.OtherValues);
			CollectionAssert.AreEqual(new string[] { "D", "E", "F" }, param.OtherValues);
		}

		[Test]
		public void Populate_ICollection_Parameter()
		{
			string[] testParams = new String[] { "-Values", "A", "B", "C", "-OtherValues", "D", "E", "F" };
			ICollectionParameter param = new ICollectionParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.IsNotNull(param.Values);
			CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, param.Values);
			Assert.IsNotNull(param.OtherValues);
			CollectionAssert.AreEqual(new string[] { "D", "E", "F" }, param.OtherValues);
		}

		[Test]
		[ExpectedException(typeof(NotSupportedException))]
		public void Cannot_Populate_Concrete_List_Parameter()
		{
			string[] testParams = new string[] { "-Values", "A", "B", "C" };
			ConcreteListParameter param = new ConcreteListParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		[ExpectedException(typeof(NotSupportedException))]
		public void Cannot_Populate_Generic_IList_Of_Non_String_Parameter()
		{
			string[] testParams = new string[] { "-Values", "1", "2", "3" };
			IListOfIntegerParameter param = new IListOfIntegerParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		[ExpectedException(typeof(ParamGateException))]
		public void Cannot_Refer_To_Property_Name_On_Custom_Parameter_Name()
		{
			string[] testParams = new string[] { "-Email", "joe@somewhere.com" };
			CustomNameParameter param = new CustomNameParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		public void Populate_Property_On_Custom_Parameter_Name()
		{
			string[] testParams = new string[] { "-E-Mail", "joe@somewhere.com" };
			CustomNameParameter param = new CustomNameParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual("joe@somewhere.com", param.Email);
		}

		[Test]
		[ExpectedException(typeof(ParamGateException))]
		public void May_Not_Have_Both_Expected_And_Optional_Tags_On_Same_Parameter()
		{
			string[] testParams = new string[] { };
			BothOptionalAndExpectedTags param = new BothOptionalAndExpectedTags();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		[Test]
		public void Basic_Configuration_Scenario_Test()
		{
			string[] testParams = new string[] { "Connect", "-Hosts", "127.0.0.1", "154.78.235.1", "-Time-Out", "60" };
			BasicConfiguration param = new BasicConfiguration();
			ParamGateHandler.ProcessArguments(testParams, param);
			Assert.AreEqual("Connect", param.Command);
			CollectionAssert.AreEqual(new string[] { "127.0.0.1", "154.78.235.1" }, param.Hosts);
			Assert.AreEqual(60, param.Timeout);
		}

		[Test]
		[ExpectedException(typeof(ParamGateException))]
		public void May_Not_Have_Multiple_Values_When_Only_One_Is_Expected()
		{
			string[] testParams = new string[] { "-Name", "Dave", "John" };
			OneExpectedParameter param = new OneExpectedParameter();
			ParamGateHandler.ProcessArguments(testParams, param);
		}

		#endregion

		#region Test Parameter Containers

		class OneExpectedParameter
		{
			[ExpectedParameter]
			public string Name { get; set; }
		}

		class OneExplicitOptionalParameter
		{
			[OptionalParameter]
			public string Message { get; set; }
		}

		class OneImplicitOptionalParameter
		{
			public string Message { get; set; }
		}

		class ExpectedDefaultParameter
		{
			[ExpectedParameter(IsDefault = true)]
			public string Message { get; set; }
		}

		class MultipleDefaultParameters
		{
			[ExpectedParameter(IsDefault = true)]
			public string Message { get; set; }

			[ExpectedParameter(IsDefault = true)]
			public string Message2 { get; set; }
		}

		class OptionalDefaultParameter
		{
			[OptionalParameter(IsDefault = true)]
			public string Message { get; set; }
		}

		class Int32Parameter
		{
			public int Number { get; set; }
		}

		class BoolParameter
		{
			public bool Setting { get; set; }
		}

		class StringArrayParameter
		{
			public string[] Values { get; set; }
		}

		class IListParameter
		{
			public IList Values { get; set; }
			public IList<string> OtherValues { get; set; }
		}

		class IEnumerableParameter
		{
			public IEnumerable Values { get; set; }
			public IEnumerable<string> OtherValues { get; set; }
		}

		class ICollectionParameter
		{
			public ICollection Values { get; set; }
			public ICollection<string> OtherValues { get; set; }
		}

		class ConcreteListParameter
		{
			public List<string> Values { get; set; }
		}

		class IListOfIntegerParameter
		{
			public IList<int> Values { get; set; }
		}

		class CustomNameParameter
		{
			[ExpectedParameter(ParameterName = "E-Mail")]
			public string Email { get; set; }
		}

		class BothOptionalAndExpectedTags
		{
			[OptionalParameter]
			[ExpectedParameter]
			public string Message { get; set; }
		}

		class BasicConfiguration
		{
			[ExpectedParameter]
			public string[] Hosts { get; set; }

			[ExpectedParameter(IsDefault = true)]
			public string Command { get; set; }

			[OptionalParameter(ParameterName = "Time-Out")]
			public int Timeout { get; set; }
		}

		#endregion
	}
}
